/**
 ******************************************************************************
 * @file    linklist_node_t.c
 * @author  Chavis.Chen
 * @version V1.0.0
 * @date    26-Sep-2018
 * @brief   This file contains the abstraction of linklist_node_t.
 ******************************************************************************
 *
 *  The MIT License
 *  Copyright (c) 2018 Chavis.Chen.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is furnished
 *  to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 *  IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 ******************************************************************************
 */


#include "debug.h"
#include "linklist.h"
#include <string.h>
#include <stdlib.h>

#define linklist_log(msg, ...)  custom_log("LINKLIST", msg, ##__VA_ARGS__)

//linklist_node_t init
OSStatus linklist_init(linklist_handle_t * linklist_handle_ptr)
{
    OSStatus err = kNoErr;

    if(! linklist_handle_ptr)
    {
        err = kParamErr;
        require_noerr_string(err, exit, "[linklist_handle_ptr] can not be NULL");
    }

    *linklist_handle_ptr = malloc(sizeof(linklist_t));
    if(! *linklist_handle_ptr)    //malloc fail
    {
        err = kNoMemoryErr;
		require_noerr_string(err, exit, "malloc fail");
    }
    memset(*linklist_handle_ptr, 0, sizeof(linklist_t));
    (*linklist_handle_ptr)->header = malloc(sizeof(linklist_node_t));
    if(! (*linklist_handle_ptr)->header)
    {
        err = kNoMemoryErr;
		require_noerr_string(err, exit, "malloc fail");
    }
    memset((*linklist_handle_ptr)->header, 0, sizeof(linklist_node_t));

exit:
    return err;
}

//push to the linklist_node_t
OSStatus linklist_push(linklist_handle_t * linklist_handle_ptr, push_dir_e dir, data_t * data, datasize_t size)
{
    OSStatus err = kNoErr;

    if(! linklist_handle_ptr)
    {
        err = kParamErr;
		require_noerr_string(err, exit, "[linklist_handle_ptr] can not be NULL");
    }

    if(! *linklist_handle_ptr)
    {
        err = kParamErr;
		require_noerr_string(err, exit, "invalid linklist_node_t handler");
    }

    switch(dir)
    {
        case DIR_FIFO:  //push to tail
            (*linklist_handle_ptr)->curt = malloc(sizeof(linklist_node_t) - 1 + size);
            if(! (*linklist_handle_ptr)->curt)
            {
                err = kNoMemoryErr;
				require_noerr_string(err, exit, "malloc fail");
            }

            memcpy((*linklist_handle_ptr)->curt->data, data, size);
            (*linklist_handle_ptr)->curt->size = size;

            if((*linklist_handle_ptr)->header->next == NULL)
            {
				(*linklist_handle_ptr)->tail = (*linklist_handle_ptr)->header;
            }

			(*linklist_handle_ptr)->tail->next = (*linklist_handle_ptr)->curt;
            (*linklist_handle_ptr)->tail = (*linklist_handle_ptr)->curt;
            (*linklist_handle_ptr)->tail->next = NULL;
            (*linklist_handle_ptr)->node_cnt++;
        break;

        case DIR_LIFO:  //push to head
            (*linklist_handle_ptr)->curt = malloc(sizeof(linklist_node_t) - 1 + size);
            if(! (*linklist_handle_ptr)->curt)
            {
                err = kNoMemoryErr;
				require_noerr_string(err, exit, "malloc fail");
            }

            memcpy((*linklist_handle_ptr)->curt->data, data, size);
            (*linklist_handle_ptr)->curt->size = size;
            (*linklist_handle_ptr)->curt->next = (*linklist_handle_ptr)->header->next;

            if((*linklist_handle_ptr)->header->next == NULL)
            {
				(*linklist_handle_ptr)->tail = (*linklist_handle_ptr)->curt;
            }

            (*linklist_handle_ptr)->header->next = (*linklist_handle_ptr)->curt;
            (*linklist_handle_ptr)->node_cnt++;
        break;

        default:
            err = kParamErr;
            require_noerr_string(err, exit, "illegal linklist_node_t inserting direction");
        break;
    }

exit:
    return err;
}


// pop from head
// we will malloc a block of new memory to save the data to be poped,
// and [*data_pp] will point to this memory,
// you should call [linklist_mem_free] func to free this memory when no more used.
OSStatus linklist_pop(linklist_handle_t * linklist_handle_ptr, data_t * * data_pp, datasize_t * data_len)
{
    OSStatus err = kNoErr;
    datasize_t   data_len_temp = 0;

    if(! linklist_handle_ptr)
    {
        err = kParamErr;
		require_noerr_string(err, exit, "[linklist_handle_ptr] can not be NULL");
    }

    if(! *linklist_handle_ptr)
    {
        err = kParamErr;
		require_noerr_string(err, exit, "invalid linklist_node_t handler");
    }

	if(! data_pp)
	{
		err = kParamErr;
		require_noerr_string(err, exit, "[data_pp] can not be NULL");
	}

    if((*linklist_handle_ptr)->header->next)   //node exist
    {
		data_len_temp = (*linklist_handle_ptr)->header->next->size;

        *data_pp = malloc(data_len_temp);

		require_action_string(*data_pp, exit, err = kNoMemoryErr, "malloc fail");

		memcpy(*data_pp, (*linklist_handle_ptr)->header->next->data, data_len_temp);

		if(data_len != NULL)
			*data_len = data_len_temp;

		linklist_node_t * temp_node = (*linklist_handle_ptr)->header->next;

		(*linklist_handle_ptr)->header->next = temp_node->next;
		(*linklist_handle_ptr)->node_cnt--;

		memset(temp_node, 0, sizeof(linklist_node_t) - 1 + temp_node->size);
		free(temp_node);
    }
    else
    {
        err = kNotExistErr;
		require_noerr_string(err, exit, "linklist_node_t node is not exist");
    }

exit:
    return err;
}

//llinklist get node cnt
OSStatus linklist_get_node_cnt(linklist_handle_t * linklist_handle_ptr, node_cnt_t * nodeCnt)
{
    OSStatus err = kNoErr;

    if(! linklist_handle_ptr)
    {
		err = kParamErr;
		require_noerr_string(err, exit, "para can not be NULL");
    }

    if(! *linklist_handle_ptr)
    {
		err = kParamErr;
		require_noerr_string(err, exit, "invalid linklist_node_t handler");
    }

    if(! nodeCnt)
    {
		err = kParamErr;
		require_noerr_string(err, exit, "para can not be NULL");
    }

    *nodeCnt = (*linklist_handle_ptr)->node_cnt;

exit:
    return err;
}

//linklist_node_t node find
OSStatus linklist_node_find(linklist_handle_t * linklist_handle_ptr, uint32_t i, linklist_node_t * * dest_node)
{
    OSStatus err = kNoErr;
	int 		 j = 0;

    if(! linklist_handle_ptr)
    {
        err = kParamErr;
		require_noerr_string(err, exit, "para can not be NULL");
    }

    if(! *linklist_handle_ptr)
    {
        err = kParamErr;
		require_noerr_string(err, exit, "invalid linklist_node_t handler");
    }

	if(! dest_node)
	{
        err = kParamErr;
		require_noerr_string(err, exit, "para can not be NULL");
	}

    linklist_node_t * temp_node = (*linklist_handle_ptr)->header->next;

    if(i >= (*linklist_handle_ptr)->node_cnt)
    {
        err = kNotExistErr;
		require_noerr_string(err, exit, "linklist_node_t node is not exist");
    }

    for(j=0; j<=i; j++)
    {
        if(j == i)
        {
            *dest_node = temp_node;
            break;
        }
        temp_node = temp_node->next;
    }

exit:
    return err;
}

//delete a node from the linklist_node_t, i starts from 0
OSStatus linklist_node_delete(linklist_handle_t * linklist_handle_ptr, uint32_t i)
{
    OSStatus err = kNoErr;

    if(! linklist_handle_ptr)
    {
        err = kParamErr;
		require_noerr_string(err, exit, "para can not be NULL");
    }

    if(! *linklist_handle_ptr)
    {
        err = kParamErr;
		require_noerr_string(err, exit, "invalid linklist_node_t handler");
    }

    if(i >= (*linklist_handle_ptr)->node_cnt)
    {
        err = kNotExistErr;
		require_noerr_string(err, exit, "linklist_node_t node is not exist");
    }

    linklist_node_t * pre_node_i = NULL;
    linklist_node_t * next_node_i = NULL;
	linklist_node_t * curt_node_i = NULL;

	linklist_node_find(linklist_handle_ptr, i, &curt_node_i);

    if(i == 0)  //node i is the first one, has no pre node
    {
        (*linklist_handle_ptr)->header->next = curt_node_i->next;
    }
    else
    {
        linklist_node_find(linklist_handle_ptr, i-1, &pre_node_i);
        err = linklist_node_find(linklist_handle_ptr, i+1, &next_node_i);
        if(err != kNoErr)   //node i is the last one, has no next node
        {
            pre_node_i->next = NULL;
			(*linklist_handle_ptr)->tail = pre_node_i;
			err = kNoErr;
        }
        else
        {
            pre_node_i->next = next_node_i;
        }
    }

	memset(curt_node_i, 0, sizeof(linklist_node_t) - 1 + curt_node_i->size);
    free(curt_node_i);
    (*linklist_handle_ptr)->node_cnt--;

exit:
    return err;
}

//linklist_node_t clean
OSStatus linklist_clean(linklist_handle_t * linklist_handle_ptr)
{
    OSStatus err = kNoErr;
    node_cnt_t node_num = 0;
	int		   i = 0;

    if(! linklist_handle_ptr)
    {
        err = kParamErr;
		require_noerr_string(err, exit, "para can not be NULL");
    }

    if(! *linklist_handle_ptr)
    {
        err = kParamErr;
		require_noerr_string(err, exit, "invalid linklist_node_t handler");
    }

    node_num = (*linklist_handle_ptr)->node_cnt;

    for(i=0; i<node_num; i++)
    {
        err = linklist_node_delete(linklist_handle_ptr, 0);
        require_noerr_string(err, exit, "linklist_node_delete error");
    }

exit:
    return err;
}

//linklist_node_t deinit
OSStatus linklist_deinit(linklist_handle_t * linklist_handle_ptr)
{
    OSStatus err = kNoErr;

    if(! linklist_handle_ptr)
    {
        err = kParamErr;
		require_noerr_string(err, exit, "para can not be NULL");
    }

    if(! *linklist_handle_ptr)
    {
        err = kParamErr;
		require_noerr_string(err, exit, "invalid linklist_node_t handler");
    }

    err = linklist_clean(linklist_handle_ptr);
    if(err != kNoErr)
    {
        err = kInternalErr;
		require_noerr_string(err, exit, "linklist_node_t empty fail");
    }

	if((*linklist_handle_ptr)->header != NULL)
	{
		memset((*linklist_handle_ptr)->header, 0, sizeof(linklist_node_t));
		free((*linklist_handle_ptr)->header);
	}

    memset(*linklist_handle_ptr, 0, sizeof(linklist_t));
    free(*linklist_handle_ptr);
    * linklist_handle_ptr = NULL;

exit:
    return err;
}

void linklist_mem_free(void * mem_ptr)
{
	free(mem_ptr);
}


